//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <string.h>
#include <assert.h>
#include <malloc.h>
#include <eltypes.h>
#include <errno.h>
#include <_pubcrt.h>

#define _NO_INCLIST
#include <elasys_server.h>

_ELASTOS_NAMESPACE_USING

//
// The other definition is in \kernel\major\inc\mantle\asdef.h
//
#ifndef _win32
#define USER_ENVIRONMENT_SIZE       0x2000      // 8KB
#else
#define USER_ENVIRONMENT_SIZE       0x3000      // 12KB
#endif
#define MAX_ENVIRONMENT_ITEM        127

#define ROUND4(n)   (((n)+3)&~3)    // round up to multiple of 4 bytes

//
//  Memory structure of environment segment:
//
//  |<-- max item + 1 -->|<-- max item + 1 ->|<------ c_iMaxBufferSize --->|
//  +---------------+----+--------------+----+-----------+-----------+-----+
//  | array of wenv |NULL| array of env |NULL| env block | env block | ... |
//  +---------------+----+--------------+----+-----------+-----------+-----+
//  ^                    ^                   ^
//  |                    |__ _environ        |__ c_pBeginOfBuffer
//  |____ _wenviron
//

wchar_t **_wenviron;
char **_environ;

static int s_iEnvCount = 0;

byte * c_pBeginOfBuffer;
int    c_iMaxBufferSize;

//
//  Memory structure of ONE environment block:
//
//  |<-- 4-->|<---- BLOCK_LENGTH(index) (# of bytes) ------>|
//  +--------+-------------------------+--------------------+
//  | length |//// (Unicode string) ///|///// (string) /////|
//  +--------+-------------------------+--------------------+
//  ^        ^                         ^                    ^
//  |        |__ WENV_VALUE(index)     |__ ENV_VALUE(index) |__ BLOCK_END(index)
//  |____ BLOCK_BEGIN(index)
//
#define WENV_VALUE(index)   (*(_wenviron + (index)))
#define ENV_VALUE(index)    (*(_environ + (index)))

#define BLOCK_BEGIN(index)  ((byte*)WENV_VALUE(index) - sizeof(int))
#define BLOCK_LENGTH(index) (*(int*)BLOCK_BEGIN(index))
#define BLOCK_END(index)    ((byte*)WENV_VALUE(index) + BLOCK_LENGTH(index))

EXTERN_C IMutex *EnvironMutex();

static int AllocEnvBlock(int iEnvStrLen);
static void FreeEnvBlock(int index);
static int ReallocEnvBlock(int index, int iEnvStrLen);

EXTERN_C void CDECL initenv(wchar_t **ppwszEnvBase)
{
    _wenviron = (wchar_t **)ppwszEnvBase;

    _environ = (char **)(_wenviron + MAX_ENVIRONMENT_ITEM + 1);
    c_pBeginOfBuffer = (byte *)(_environ + MAX_ENVIRONMENT_ITEM + 1);
    c_iMaxBufferSize = (uint_t)_wenviron + USER_ENVIRONMENT_SIZE \
                                                - (uint_t)c_pBeginOfBuffer;
}

EXTERN_C void CDECL freeenviron()
{
}

EXTERN_C char * CDECL GetEnv(const char *envstring)
{
    uint_t len;
    char **ppszTemp;
    char *retval;

    if (NULL == envstring) return NULL;

    ppszTemp = _environ;
    len = strlen(envstring);

    WaitResult wr;
    EnvironMutex()->Lock(&wr);

    while (*ppszTemp) {
        if (strlen(*ppszTemp) > len && (*(*ppszTemp + len) == '=')
            && (0 == _strnicmp((const char *)*ppszTemp, envstring, len))) {
            retval = *ppszTemp + len + 1;
            EnvironMutex()->Unlock();
            return retval;
        }

        ppszTemp++;
    }

    EnvironMutex()->Unlock();
    return NULL;
}

EXTERN_C wchar_t * CDECL WGetEnv(const wchar_t *envstring)
{
    uint_t len;
    wchar_t **ppwszTemp;
    wchar_t *retval;

    if (NULL == envstring) {
        return NULL;
    }

    ppwszTemp = _wenviron;
    len = wcslen(envstring);

    WaitResult wr;
    EnvironMutex()->Lock(&wr);

    while (*ppwszTemp) {
        if (wcslen(*ppwszTemp) > len && (*(*ppwszTemp + len) == L'=')
            && (0 == _wcsnicmp((const wchar_t *)*ppwszTemp, envstring, len))) {
            retval = *ppwszTemp + len + 1;
            EnvironMutex()->Unlock();
            return retval;
        }

        ppwszTemp++;
    }

    EnvironMutex()->Unlock();
    return NULL;
}

EXTERN_C ECode PutEnv(const char *strenv, const wchar_t *wcsenv)
{
    wchar_t *equal;
    wchar_t **ppwszTemp;
    int     index = -1;
    Boolean    bNeedRemove;

    //
    // Find the '=' and verify it is not the first character in string.
    //
    if (!strenv || !wcsenv
        || ((equal = wcschr(wcsenv, L'=')) == NULL)
        || wcsenv == equal) {
        return ECODE_FROM_ERRNO(EINVAL);
    }

    int iKeyLen = equal - wcsenv;
    int iWcsLen = ROUND4((wcslen(wcsenv) + 1) * sizeof(wchar_t));
    int iStrLen = ROUND4((strlen(strenv) + 1) * sizeof(char));
    int iBlockSize = sizeof(int) + iWcsLen + iStrLen;

    // See if need to remove?
    //
    if (L'\0' == *(equal + 1)) {
        bNeedRemove = 1;
    }
    else {
        bNeedRemove = 0;
    }

    WaitResult wr;
    EnvironMutex()->Lock(&wr);

    if (!s_iEnvCount) {
        while (WENV_VALUE(s_iEnvCount)) {
            s_iEnvCount++;
        }
    }

    // See if the string is already in the environment
    //
    for (ppwszTemp = _wenviron; NULL != *ppwszTemp; ppwszTemp++) {
        if ((*ppwszTemp)[iKeyLen] == L'='
            && _wcsnicmp(wcsenv, *ppwszTemp, iKeyLen) == 0) {
            // String is already in the environment
            //
            index = ppwszTemp - _wenviron;
            break;
        }
    }

    if (-1 == index) {
        //
        // String is NOT in the environment.
        //

        // If need to remove, just return succeeded.
        //
        if (bNeedRemove) {
            EnvironMutex()->Unlock();
            return NOERROR;
        }

        // Alloc one block for new string.
        index = AllocEnvBlock(iBlockSize);
    }
    else {
        //
        // String is already in the environment.
        //

        // See if need to remove?
        //
        if (bNeedRemove) {
            FreeEnvBlock(index);
            EnvironMutex()->Unlock();
            return NOERROR;
        }
        else {
            index = ReallocEnvBlock(index, iBlockSize);
        }
    }

    // Alloc block failed.
    //
    if (-1 == index) {
        EnvironMutex()->Unlock();
        return ECODE_FROM_ERRNO(ENOMEM);
    }

    ENV_VALUE(index)    = (char*)((byte *)WENV_VALUE(index) + iWcsLen);
    BLOCK_LENGTH(index) = iBlockSize;

    // Copy to new block.
    //
    wcscpy(WENV_VALUE(index), wcsenv);
    strcpy(ENV_VALUE(index), strenv);

    EnvironMutex()->Unlock();
    return NOERROR;
}

EXTERN_C char *** CDECL __p__environ(void)
{
    return &_environ;
}

EXTERN_C wchar_t *** CDECL __p__wenviron(void)
{
    return &_wenviron;
}

//
// return value: index of new block, -1 for alloc failed.
//
static int AllocEnvBlock(int iBlockSize)
{
    if (!WENV_VALUE(0) && (iBlockSize > c_iMaxBufferSize)
        || (MAX_ENVIRONMENT_ITEM == s_iEnvCount)) {
        // no space for new block
        return -1;
    }

    byte *pBeginOfNewBlock = (byte*)c_pBeginOfBuffer;

    // Alloc space for a new block
    int index;
    for (index = 0; index < s_iEnvCount; index++) {
        if (iBlockSize <= BLOCK_BEGIN(index) - pBeginOfNewBlock) {
            break;
        }
        pBeginOfNewBlock = BLOCK_END(index);
    }

    if (index == s_iEnvCount) {
        if (iBlockSize > ((byte*)c_pBeginOfBuffer + c_iMaxBufferSize
            - pBeginOfNewBlock)) {
            // no space for new block
            return -1;
        }
    }
    else {
        // Move down items after index
        for (int i = s_iEnvCount; i > index; i--) {
            ENV_VALUE(i)  = ENV_VALUE(i - 1);
            WENV_VALUE(i) = WENV_VALUE(i - 1);
        }
    }

    //
    // Now alloc OK! Variable "index" is the index of new block.
    // Increase environment counter.
    //
    s_iEnvCount++;

    WENV_VALUE(index)   = (wchar_t*)(pBeginOfNewBlock + sizeof(int));

    return index;
}

static void FreeEnvBlock(int index)
{
    assert((0 <= index) && (MAX_ENVIRONMENT_ITEM > index));

    // Just move up items after index
    //
    do {
        ENV_VALUE(index)  = ENV_VALUE(index + 1);
        WENV_VALUE(index) = WENV_VALUE(index + 1);
        index++;
    } while (WENV_VALUE(index - 1));

    s_iEnvCount--;
}

static int ReallocEnvBlock(int index, int iBlockSize)
{
    assert((0 <= index) && (MAX_ENVIRONMENT_ITEM > index));

    int retval;

    int iCurBlokSize;
    if (index < s_iEnvCount - 1) {
        iCurBlokSize = BLOCK_BEGIN(index + 1) - BLOCK_BEGIN(index);
    }
    else {
        iCurBlokSize = (byte*)c_pBeginOfBuffer + c_iMaxBufferSize
            - BLOCK_BEGIN(index);
    }

    if (iBlockSize <= iCurBlokSize) {
        return index;
    }

    retval = AllocEnvBlock(iBlockSize);
    if (-1 == retval) {
        return -1; // TODO:
    }

    if (retval <= index) {
        index++;
    }
    else {
        retval--;
    }
    FreeEnvBlock(index);
    return retval;
}
